/*-
 * Scala tree grammar for semantic checking and building the tree model.
 *
 * Copyright (c) 2010 Alex Suhan
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer
 * in this position and unchanged.
 * 2. Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

tree grammar ScalaTreeChecker;

options {
    tokenVocab = ScalaTreeBuilder;
    ASTLabelType = CommonTree;
}

@header {
    import java.util.Map;
    import java.util.HashMap;
    import java.util.Iterator;
}

@members {
    private static int fixedString(String str, StringBuffer result)
    {
        int pos;
        int origPos = 0;
        int nlCount = 0;
        
        /* sirul este deja ok */
        if (str.indexOf("\\") == -1)
            return -1;
        
        while (origPos < str.length() && (pos = str.indexOf("\\", origPos)) != -1)
        {
            if (str.charAt(pos + 1) == 'b')
                result.append(str.substring(origPos, pos)).append('\b');
            else if (str.charAt(pos + 1) == 't')
                result.append(str.substring(origPos, pos)).append('\t');
            else if (str.charAt(pos + 1) == 'n')
                result.append(str.substring(origPos, pos)).append('\n');
            else if (str.charAt(pos + 1) == 'f')
                result.append(str.substring(origPos, pos)).append('\f');
            else
            {
                /* literal extins pe mai multe linii */
                if (str.charAt(pos + 1) == '\n')
                    nlCount++;
                result.append(str.substring(origPos, pos)).append(str.charAt(pos + 1));
            }
            origPos = pos + 2;
        }
        
        if (origPos < str.length())
            result.append(str.substring(origPos));
        
        return nlCount;
    }
    
    private static class IdInfo
    {
        IdScope scope;
        String type;
        
        IdInfo(IdScope scope, String type)
        {
            this.scope = scope;
            this.type = type;
        }
    }
    
    private static IdInfo getIdType(String id, String className, String methodName)
    {
        ScalaClassInfo classInfo =
            ScalaMapsBuilder.classTable.get(className);
        ScalaMethodInfo methodInfo =
            classInfo.getMethod(methodName);
        for (ScalaFormal formal: methodInfo.getFormalParams())
            if (formal.getName().equals(id))
                return new IdInfo(IdScope.PARAMETER, formal.getType());
        ScalaLocalVar localVarInfo = methodInfo.getLocalVar(id);
        if (localVarInfo != null)
            return new IdInfo(IdScope.LOCAL, localVarInfo.typeName());
        ScalaAttrInfo attrInfo = classInfo.getAttribute(id);
        if (attrInfo != null)
            return new IdInfo(IdScope.CLASS, attrInfo.getType());
        return null;
    }
    
    private static void error(int line, int pos, String message)
    {
        System.err.println("" + line + ":" + pos + ": " + message);
        System.exit(-1);
    }

    static Map<String, Integer> stringTable = new HashMap<String, Integer>();
    static Map<Integer, Integer> integerTable = new HashMap<Integer, Integer>();
    static int stringIdx = 0;
    static int integerIdx = 0;
    
    private static void fillSymTables()
    {
        integerTable.put(0, integerIdx);
        integerIdx++;
        stringTable.put(ScalaConstants.ObjectClassname, stringIdx);
        integerTable.put(ScalaConstants.ObjectClassname.length(), integerIdx);
        stringIdx++;
        integerIdx++;
        stringTable.put(ScalaConstants.IntegerClassname, stringIdx);
        integerTable.put(ScalaConstants.IntegerClassname.length(), integerIdx);
        stringIdx++;
        integerIdx++;
        stringTable.put(ScalaConstants.BooleanClassname, stringIdx);
        integerTable.put(ScalaConstants.BooleanClassname.length(), integerIdx);
        stringIdx++;
        integerIdx++;
        stringTable.put(ScalaConstants.StringClassname, stringIdx);
        integerTable.put(ScalaConstants.StringClassname.length(), integerIdx);
        stringIdx++;
        integerIdx++;
        stringTable.put(ScalaConstants.ConsoleClassname, stringIdx);
        integerTable.put(ScalaConstants.ConsoleClassname.length(), integerIdx);
        stringIdx++;
        integerIdx++;
    }
    
    // search method info in the inheritance tree starting with 'className'
    static ScalaMethodInfo getMethod(String methodName, String className)
    {
        Iterator<String> inhChain =
            ScalaMapsBuilder.inhGraph.getInheritancePath(className);
        while (inhChain.hasNext()) {
            ScalaClassInfo classInfo =
                ScalaMapsBuilder.classTable.get(inhChain.next());
            ScalaMethodInfo methodInfo =
                classInfo.getMethod(methodName);
            if (methodInfo != null)
                return methodInfo;
        }
        return null;
    }

    static
    {
        fillSymTables();
    }
    
    static void addFilenameToSymtable(String fileName)
    {
        stringTable.put(fileName, stringIdx);
        integerTable.put(fileName.length(), integerIdx);
        stringIdx++;
        integerIdx++;
    }
}


program
    :   ^(DiceProgram classDef+)
    ;

classDef
scope
{
    String crtClass;
}
    :   ^(Class
        ^(Name name=ID
        {
            $classDef::crtClass = $name.text;
            stringTable.put($name.text, stringIdx);
            integerTable.put($name.text.length(), integerIdx);
            stringIdx++;
            integerIdx++;
        })
        .? features)
    ;

features
    :    ^(Features feature*)
    ;

feature returns [ScalaFeatureInfo info]
scope
{
    String crtMethod;
}
    :    ^(FeatureMethod .? ^(Name name=ID { $feature::crtMethod = $name.text; }) . .? ^(MethodBody expr))
        {
            ScalaClassInfo classInfo =
                ScalaMapsBuilder.classTable.get($classDef::crtClass);
            ScalaMethodInfo methodInfo = classInfo.getMethod($name.text);
            methodInfo.setBody($expr.info);
        }
    |   ^(FeatureAttrib ^(Name name=ID) ^(Type type=ID)
        (^(InitExpr expr
        {
            ScalaClassInfo classInfo =
                ScalaMapsBuilder.classTable.get($classDef::crtClass);
            ScalaAttrInfo attrInfo = classInfo.getAttribute($name.text);
            attrInfo.setInit($expr.info);
        }))?
        )
    ;

expr returns [ScalaExpression info]
    :   ^(AS ID rhs=expr)
        {
            IdInfo idInfo =
                getIdType($ID.text, $classDef::crtClass, $feature::crtMethod);
            $info = new ScalaAssign($ID.text, idInfo.scope, idInfo.type, $rhs.info);
        }
    |   ^(LocalVar ^(Name name=ID) ^(Type type=ID)
        {
            ScalaExpression initExpr = null;
        }
        (^(InitExpr init=expr { initExpr = $init.info; }))?)
        {
            ScalaLocalVar localVarInfo =
                new ScalaLocalVar($name.text, $type.text, $init.info);
            ScalaMethodInfo methodInfo = getMethod($feature::crtMethod, $classDef::crtClass);
            if (methodInfo == null)
                error($Name.line, $Name.pos, "Method " + $name.text + " not found.");
            methodInfo.putLocalVar($name.text, localVarInfo);
            $info = localVarInfo;
        }
    |   KW_THIS
        {
            $info = new ScalaThis();
        }
    |    ^(DOT
        {
            ScalaExpressionList effParams = new ScalaExpressionList();
        }
        ^(CallingObject caller=expr)
        ^(CalledMethod ID)
        {
            ScalaMethodInfo methodInfo = getMethod($ID.text, $caller.info.typeName());
        }
        ^(LPAREN ((e=expr { effParams.add($e.info); } )+)?)
        )
        {
            $info = new ScalaMethodCall($caller.info, $ID.text,
                                        methodInfo.getReturnedType(), effParams,
                                        $LPAREN.line);
        }
    |    ^(SelfMethodCall
        {
            ScalaExpressionList effParams = new ScalaExpressionList();
        }
        ^(CalledMethod ID)
        ^(LPAREN ((e=expr { effParams.add($e.info); } )+)?)
        )
        {
            ScalaClassInfo classInfo =
                ScalaMapsBuilder.classTable.get($classDef::crtClass);
            ScalaMethodInfo methodInfo = getMethod($ID.text, $classDef::crtClass);
            if (methodInfo == null)
                error($SelfMethodCall.line, $SelfMethodCall.pos,
                      "Method " + $ID.text + " not found in class " + $classDef::crtClass + ".");
            $info = new ScalaMethodCall(new ScalaThis(), $ID.text,
                                        methodInfo.getReturnedType(), effParams,
                                        $LPAREN.line);
        }
    |    ^(IfThenElse ^(CondExpr cond=expr) ^(TrueBranch thenExpr=expr) ^(FalseBranch elseExpr=expr))
        {
            $info = new ScalaIfThenElse($cond.info, $thenExpr.info, $elseExpr.info);
        }
    |    ^(While ^(CondExpr cond=expr) ^(Body body=expr))
        {
            $info = new ScalaWhile($cond.info, $body.info);
        }
    |    ^(LEFT_BRACE
        {
            ScalaExpressionList exprSequence = new ScalaExpressionList();
        }
        exprList [exprSequence]
        )
        {
            ScalaExpression lastInSeq = $exprList.last;
            $info = new ScalaBlock(exprSequence);
        }
    |    ^(KW_NEW ID)
        {
            $info = new ScalaNew($ID.text);
        }
    |    ^(ADD left=expr right=expr)
        {
            $info = new ScalaBinary($left.info, $right.info, ScalaConstants.ScalaBinaryOp.ADD);
        }
    |    ^(SUB left=expr right=expr)
        {
            $info = new ScalaBinary($left.info, $right.info, ScalaConstants.ScalaBinaryOp.SUB);
        }
    |    ^(MUL left=expr right=expr)
        {
            $info = new ScalaBinary($left.info, $right.info, ScalaConstants.ScalaBinaryOp.MUL);
        }
    |    ^(DIV left=expr right=expr)
        {
            $info = new ScalaBinary($left.info, $right.info, ScalaConstants.ScalaBinaryOp.DIV);
        }
    |    ^(LT left=expr right=expr)
        {
            $info = new ScalaBinary($left.info, $right.info, ScalaConstants.ScalaBinaryOp.LT);
        }
    |    ^(LE left=expr right=expr)
        {
            $info = new ScalaBinary($left.info, $right.info, ScalaConstants.ScalaBinaryOp.LE);
        }
    |    ^(EQ left=expr right=expr)
        {
            $info = new ScalaBinary($left.info, $right.info, ScalaConstants.ScalaBinaryOp.EQ);
        }
    |    ^(GE left=expr right=expr)
        {
            $info = new ScalaBinary($left.info, $right.info, ScalaConstants.ScalaBinaryOp.GE);
        }
    |    ^(GT left=expr right=expr)
        {
            $info = new ScalaBinary($left.info, $right.info, ScalaConstants.ScalaBinaryOp.GT);
        }
    |    ^(NOT e=expr)
        {
            $info = new ScalaUnary($e.info, ScalaConstants.ScalaUnaryOp.NOT);
        }
    |    ID
        {
            IdInfo idInfo =
                getIdType($ID.text, $classDef::crtClass, $feature::crtMethod);
            $info = new ScalaObject($ID.text, idInfo.scope, idInfo.type);
        }
    |    L_INT
        {
            int value = Integer.parseInt($L_INT.text);
            $info = new ScalaIntConst(value);
        }
    |    L_STRING
        {            
            StringBuffer fixed = new StringBuffer();
            String toFix = $L_STRING.text.substring(1, $L_STRING.text.length() - 1);
            int nlCount = fixedString(toFix, fixed);
            if (nlCount == -1) {
                stringTable.put(toFix, stringIdx);
                integerTable.put(toFix.length(), integerIdx);
                stringIdx++;
                integerIdx++;
                $info = new ScalaStringConst(toFix);
            }
            else {
                stringTable.put(fixed.toString(), stringIdx);
                integerTable.put(fixed.toString().length(), integerIdx);
                stringIdx++;
                integerIdx++;
                $info = new ScalaStringConst(fixed.toString());
            }
        }
    |    KW_TRUE
        {
            $info = new ScalaBoolConst(true);
        }
    |    KW_FALSE
        {
            $info = new ScalaBoolConst(false);
        }
    ;

exprList [ScalaExpressionList info] returns [ScalaExpression last]
    :    (expr { $last = $expr.info; $info.add(last); } )+
    ;
